﻿namespace DomainServices.Tools.SourceLocation
{
    using DomainServices.Tools;
    using DomainServices.Tools.Pdb.SymStore;
    using DomainServices.Tools.SharedTypes;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.SymbolStore;
    using System.Globalization;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.ServiceModel.DomainServices;

    internal class PdbSourceFileProviderFactory : ISourceFileProviderFactory
    {
        private ILogger _logger;
        private string _symbolSearchPath;

        public PdbSourceFileProviderFactory(string symbolSearchPath, ILogger logger)
        {
            this._logger = logger;
            this._symbolSearchPath = symbolSearchPath;
        }

        public ISourceFileProvider CreateProvider()
        {
            return new PdbSourceFileProvider(this._symbolSearchPath, this._logger);
        }

        internal class PdbSourceFileProvider : ISourceFileProvider, IDisposable
        {
            private ILogger _logger;
            private Dictionary<Assembly, ISymbolReader> _symbolReadersByType = new Dictionary<Assembly, ISymbolReader>();
            private string _symbolSearchPath;

            internal PdbSourceFileProvider(string symbolSearchPath, ILogger logger)
            {
                this._symbolSearchPath = symbolSearchPath;
                this._logger = logger;
            }

            private ISymbolReader CreateSymbolReader(Assembly assembly)
            {
                ISymbolReader readerForFile = null;
                string location = assembly.Location;
                try
                {
                    readerForFile = SymbolAccess.GetReaderForFile(location, this._symbolSearchPath);
                }
                catch (COMException exception)
                {
                    if (this._logger != null)
                    {
                        this._logger.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.Failed_To_Open_PDB, new object[] { location, exception.Message }));
                    }
                }
                return readerForFile;
            }

            public void Dispose()
            {
                foreach (ISymbolReader reader in this._symbolReadersByType.Values)
                {
                    IDisposable disposable = reader as IDisposable;
                    if (disposable != null)
                    {
                        disposable.Dispose();
                    }
                }
                this._symbolReadersByType.Clear();
            }

            public string GetFileForMember(MemberInfo memberInfo)
            {
                string fileForMember = null;
                Type declaringType = memberInfo.DeclaringType;
                ISymbolReader reader = this[declaringType];
                if (reader != null)
                {
                    MethodBase methodBase = memberInfo as MethodBase;
                    if (methodBase != null)
                    {
                        return GetFileForMethod(reader, methodBase);
                    }
                    PropertyInfo info = memberInfo as PropertyInfo;
                    if (info == null)
                    {
                        return fileForMember;
                    }
                    methodBase = info.GetGetMethod() ?? info.GetSetMethod();
                    if (methodBase != null)
                    {
                        fileForMember = this.GetFileForMember(methodBase);
                    }
                }
                return fileForMember;
            }

            private static string GetFileForMethod(ISymbolReader reader, MethodBase methodBase)
            {
                int metadataToken = methodBase.MetadataToken;
                ISymbolMethod method = (reader == null) ? null : reader.GetMethod(new SymbolToken(metadataToken));
                if (method != null)
                {
                    int sequencePointCount = method.SequencePointCount;
                    int[] offsets = new int[sequencePointCount];
                    ISymbolDocument[] documents = new ISymbolDocument[sequencePointCount];
                    int[] columns = new int[sequencePointCount];
                    int[] endColumns = new int[sequencePointCount];
                    int[] lines = new int[sequencePointCount];
                    int[] endLines = new int[sequencePointCount];
                    method.GetSequencePoints(offsets, documents, lines, columns, endLines, endColumns);
                    ISymbolDocument[] documentArray2 = documents;
                    int index = 0;
                    while (index < documentArray2.Length)
                    {
                        ISymbolDocument document = documentArray2[index];
                        return document.URL.ToString();
                    }
                }
                return null;
            }

            internal ISymbolReader this[Type type]
            {
                get
                {
                    Assembly key = type.Assembly;
                    ISymbolReader reader = null;
                    if (!this._symbolReadersByType.TryGetValue(key, out reader))
                    {
                        if (!key.IsSystemAssembly())
                        {
                            reader = this.CreateSymbolReader(key);
                        }
                        this._symbolReadersByType[key] = reader;
                    }
                    return reader;
                }
            }
        }
    }
}

